单片机C语言知识点全攻略(完结篇)

控制/MCU

1883人已加入

描述

       电子发烧友网讯:继《单片机学习知识点全攻略》得到广大读者好评,根据有网友提出美中不足的是所用单片机编程语言为汇编,基于此,电子发烧友网再接再厉再次为读者诚挚奉上非常详尽的《单片机C语言知识点全攻略》系列单片机C语言学习教程,本教程共分为四部分,本文为第四部分,也是完结篇,主要知识点如下所示。

参阅相关系列文章,

单片机C语言知识点全攻略(一)
单片机C语言知识点全攻略(二)
单片机C语言知识点全攻略(三)


第四部分(完结篇)知识点:

  第十五课 C51数组的使用

  第十六课 C51指针的使用

  第十七课 C51结构、联合和枚举的使用

  附录(运算符优先级和结合性等)


 

  第十五课、C51数组的使用

  前面的文章中,都是介绍单个数据变量的使用,在“走马灯”等的例子中略有使用到数组,不难看出,数组不过就是同一类型变量的有序集合。形象的能这样去理解,就像一个 学校在操场上排队,每一个级代表一个数据类型,每一个班级为一个数组,每一个学生就是 数组中的一个数据。数据中的每个数据都能用唯一的下标来确定其位置,下标能是一维 或多维的。就如在学校的方队中要找一个学生,这个学生在 I 年级 H 班 X 组 Y 号的,那么 能把这个学生看做在 I 类型的 H 数组中(X,Y)下标位置中。数组和普通变量一样,要求先定义了才能使用,下面是定义一维或多维数组的方式:

C51单片机

 

  “数据类型”是指数组中的各数据单元的类型,每个数组中的数据单元只能是同一数据

  类型。“数组名”是整个数组的标识,命名方法和变量命名方法是一样的。在编译时系统会 根据数组大小和类型为变量分配空间,数组名能说就是所分配空间的首地址的标识。“常 量表达式”是表示数组的长度和维数,它必须用“[]”括起,括号里的数不能是变量只能是 常量。

  unsigned int xcount [10]; //定义无符号整形数组,有 10 个数据单元

  char inputstring [5]; //定义字符形数组,有 5 个数据单元

  float outnum [10],[10];//定义浮点型数组,有 100 个数据单元

  在 C 语言中数组的下标是从 0 开始的而不是从 1 开始,如一个具有 10 个数据单元的数

  组 count,它的下标就是从 count[0]到 count[9],引用单个元素就是数组名加下标,如 count[1] 就是引用 count 数组中的第 2 个元素,如果错用了 count[10]就会有错误出现了。还有一点要 注意的就是在程序中只能逐个引用数组中的元素,不能一次引用整个数组,但是字符型的数 组就能一次引用整个数组。

  数组也是能赋初值的。在上面介绍的定义方式只适用于定义在内存 DATA 存储器使 用的内存,有的时候我们需要把一些数据表存放在数组中,通常这些数据是不用在程序中改 变数值的,这个时候就要把这些数据在程序编写时就赋给数组变量。因为 51 芯片的片内 RAM 很有限,通常会把 RAM 分给参与运算的变量或数组,而那些程序中不变数据则应存放在片 内的 CODE 存储区,以节省宝贵的 RAM。赋初值的方式如下:

  数据类型 [存储器类型] 数组名 [常量表达式] = {常量表达式};

  数据类型 [ 存储器类型] 数组名 [ 常量表达式 1]。..。.. [ 常量表达式 N]={{ 常量表达 式}。..{常量表达式 N}};

  在定义并为数组赋初值时,开始学习的朋友一般会搞错初值个数和数组长度的关系,而致使 编译出错。初值个数必须小于或等于数组长度,不指定数组长度则会在编译时由实际的初值 个数自动设置。

  unsigned char LEDNUM[2]={12,35}; //一维数组赋初值

  int Key[2][3]={{1,2,4},{2,2,1}}; //二维数组赋初值

  unsigned char IOStr[]={3,5,2,5,3}; //没有指定数组长度,编译器自动设置

  unsigned char code skydata[]={0x02,0x34,0x22,0x32,0x21,0x12}; //数据保存在 code 区

  下面的一个简单例子是对数组中的数据进行排序,使用的是冒泡法,一来了解数组的使 用,二来掌握基本的排序算法。冒泡排序算法是一种基本的排序算法,它每次顺序取数组中 的两个数,并按需要按其大小排列,在下一次循环中则取下一次的一个数和数组中下一个数 进行排序,直到数组中的数据全部排序完成。

  #include 《AT89X51.H》

  #include 《stdio.h》

  void taxisfun (int taxis2[])

  {

  unsigned char TempCycA,TempCycB,Temp;

  for (TempCycA=0; TempCycA《=8; TempCycA++)

  for (TempCycB=0; TempCycB《=8-TempCycA; TempCycB++)

  {//TempCycB《8-TempCycA 比用 TempCycB《=8 少用很多循环

  if (taxis2[TempCycB+1]》taxis2[TempCycB]) //当后一个数大于前一个 数

  {

  Temp = taxis2[TempCycB]; //前后 2 数交换

  taxis2[TempCycB] = taxis2[TempCycB+1];

  taxis2[TempCycB+1] = Temp; //因函数参数是数组名调用形

  参的变动影响实参

  }

  }

  }

  void main(void)

  {

  int taxis[] = {113,5,22,12,32,233,1,21,129,3};

  char Text1[] = {“source data:”}; //“源数据”

  char Text2[] = {“sorted data:”}; //“排序后数据”

  unsigned char TempCyc;

  SCON = 0x50; //串行口方式 1,允许接收

  TMOD = 0x20; //定时器 1 定时方式 2

  TCON = 0x40; //设定时器 1 开始计数

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

  TL1 = 0xE8; TI = 1;

  TR1 = 1; //启动定时器

  printf(“%s ”,Text1); //字符数组的整体引用

  for (TempCyc=0; TempCyc《10; TempCyc++)

  printf(“%d ”,taxis[TempCyc]);

  printf(“ ---------- ”);

  taxisfun (taxis); //以实际参数数组名 taxis 做参数被函数调用

  printf(“%s ”,Text2);

  for (TempCyc=0; TempCyc《10; TempCyc++) //调用后 taxis 会被改变

  printf(“%d ”,taxis[TempCyc]);

  while(1);

  }

  例子中能看出,数组同样能作为函数的参数进行传递。数组做参数时是用数组名进行传递的,一个数组的数组名表示该数组的首地址,在用数组名作为函数的调用参数时,它 的传递方式是采用了地址传递,就是将实际参数数组的首地址传递给函数中的形式参数数 组,这个时候实际参数数组和形式参数数组实际上是使用了同一段内存单元,当形式参数数组在 函数体中改变了元素的值,同时也会影响到实际参数数组,因为它们是存放在同一个地址的。 上面的例子同时还使用到字符数组。字符数组中每一个数据都是一个字符,这样一个一 维的字符数组就组成了一个字符串,在 C 语言中字符串是以字符数组来表达处理的。为了 能测定字符串的长度,C 语言中规定以‘o’来做为字符串的结束标识,编译时会自动在字 符串的最后加入一个‘o’,那么要注意的是如果用一个数组要保存一个长度为 10 字节的字 符串则要求这个数组至少能保存 11 个元素。‘o’是转义字符,它的含义是空字符,它的 ASCII 码为 00H,也就是说当每一个字符串都是以数据 00H 结束的,在程序中操作字符数 据组时要注意这一点。字符数组除了能对数组中单个元素进行访问,还能访问整个数组, 其实整个访问字符数组就是把数组名传到函数中,数组名是一个指向数据存放空间的地址指 针,函数根据这个指针和‘/o’就能完整的操作这个字符数组。对于这一段所说的,能 参看下面一例 1602LCD 显示模块的驱动演示例子进行理解。这里要注意就是能用单个字

  符数组元素来进行运算,但不能用整个数组来做运算,因为数组名是指针而不是数据。

  /*============================================================

  使用 1602 液晶显示的实验例子 明浩 2004/2/27

  ==============================================================

  SMC1602A(16*2)模拟口线接线方式 连接线图:

  ---------------------------------------------------

  |LCM-----51 | LCM-----51 | LCM------51 |

  ---------------------------------------------|

  |DB0-----P1.0 | DB4-----P1.4 | RW-------P2.0 |

  |DB1-----P1.1 | DB5-----P1.5 | RS-------P2.1 |

  |DB2-----P1.2 | DB6-----P1.6 | E--------P2.2 |

  |DB3-----P1.3 | DB7-----P1.7 | VLCD 接 1K 电阻到 GND|

  ---------------------------------------------------

  [注:AT89S51 使用 12M 晶体震荡器]

  =============================================================*/

  #define LCM_RW P2_0 //定义引脚

  #define LCM_RS P2_1

  #define LCM_E P2_2

  #define LCM_Data P1

  #define Busy 0x80 //用于检测 LCM 状态字中的 Busy 标识

  #include 《at89x51.h》

  void WriteDataLCM(unsigned char WDLCM);

  void WriteCommandLCM(unsigned char WCLCM,BuysC);

  unsigned char ReadDataLCM(void); unsigned char ReadStatusLCM(void); void LCMInit(void);

  void DisplayOneChar(unsigned char X, unsigned char Y, unsigned char DData);

  void DisplayListChar(unsigned char X, unsigned char Y, unsigned char code *DData);

  void Delay5Ms(void);

  void Delay400Ms(void);

  unsigned char code cdle_net[] = {“www.51hei.com”};

  unsigned char code email[] = {“pnzwzw@51hei.com”};

  void main(void)

  {

  Delay400Ms(); //启动等待,等 LCM 讲入工作状态

  LCMInit(); //LCM 初始化

  Delay5Ms(); //延时片刻(可不要)

  DisplayListChar(0, 0, cdle_net); DisplayListChar(0, 1, email); ReadDataLCM();//测试用句无意义 while(1);

  }

  //写数据

  void WriteDataLCM(unsigned char WDLCM)

  {

  ReadStatusLCM(); //检测忙 LCM_Data = WDLCM; LCM_RS = 1;

  LCM_RW = 0;

  LCM_E = 0; //若晶体震荡器速度太高能在这后加小的延时

  LCM_E = 0; //延时

  LCM_E = 1;

  }

  //写指令

  void WriteCommandLCM(unsigned char WCLCM,BuysC) //BuysC 为 0 时忽略忙检测

  {

  if (BuysC) ReadStatusLCM(); //根据需要检测忙

  LCM_Data = WCLCM; LCM_RS = 0; LCM_RW = 0;

  LCM_E = 0;

  LCM_E = 0; LCM_E = 1;

  }

  //读数据

  unsigned char ReadDataLCM(void)

  {

  LCM_RS = 1; LCM_RW = 1; LCM_E = 0; LCM_E = 0; LCM_E = 1; return(LCM_Data);

  }

  //读状态

  unsigned char ReadStatusLCM(void)

  {

  LCM_Data = 0xFF; LCM_RS = 0; LCM_RW = 1; LCM_E = 0; LCM_E = 0; LCM_E = 1;

  while (LCM_Data & Busy); //检测忙信号

  return(LCM_Data);

  }

  void LCMInit(void) //LCM 初始化

  {

  LCM_Data = 0;

  WriteCommandLCM(0x38,0); //三次显示模式设置,不检测忙信号

  Delay5Ms(); WriteCommandLCM(0x38,0); Delay5Ms(); WriteCommandLCM(0x38,0); Delay5Ms();

  WriteCommandLCM(0x38,1); //显示模式设置,开始要求每次检测忙信号

  WriteCommandLCM(0x08,1); //关闭显示 WriteCommandLCM(0x01,1); //显示清屏 WriteCommandLCM(0x06,1); // 显示光标移动设置 WriteCommandLCM(0x0C,1); // 显示开及光标设置

  }

  //按指定位置显示一个字符

  void DisplayOneChar(unsigned char X, unsigned char Y, unsigned char DData)

  {

  Y &= 0x1;

  X &= 0xF; //限制 X 不能大于 15,Y 不能大于 1

  if (Y) X |= 0x40; //当要显示第二行时地址码+0x40; X |= 0x80; //算出指令码

  WriteCommandLCM(X, 0); //这里不检测忙信号,发送地址码

  WriteDataLCM(DData);

  }

  //按指定位置显示一串字符

  void DisplayListChar(unsigned char X, unsigned char Y, unsigned char code *DData)

  {

  unsigned char ListLength;

  ListLength = 0; Y &= 0x1;

  X &= 0xF; //限制 X 不能大于 15,Y 不能大于 1

  while (DData[ListLength]》0x20) //若到达字串尾则退出

  {

  if (X 《= 0xF) //X 坐标应小于 0xF

  {

  DisplayOneChar(X, Y, DData[ListLength]); //显示单个字符

  ListLength++; X++;

  }

  }

  }

  //5ms 延时

  void Delay5Ms(void)

  {

  unsigned int TempCyc = 5552;

  while(TempCyc--);

  }

  //400ms 延时

  void Delay400Ms(void)

  {

  unsigned char TempCycA = 5; unsigned int TempCycB; while(TempCycA--)

  {

  TempCycB=7269;

  while(TempCycB--);

  };

  }
 

  第十六课、C51指针的使用

  指针就是指变量或数据所在的存储区地址。如一个字符型的变量 STR 存放在内存单元DATA 区的 51H 这个地址中,那么 DATA 区的 51H 地址就是变量 STR 的指针。在 C 语言中 指针是一个很重要的概念,正确有效的使用指针类型的数据,能更有效的表达复杂的数据 结构,能更有效的使用数组或变量,能方便直接的处理内存或其它存储区。指针之所以 能这么有效的操作数据,是因为无论程序的指令、常量、变量或特殊寄存器都要存放在内 存单元或相应的存储区中,这些存储区是按字节来划分的,每一个存储单元都能用唯一的 编号去读或写数据,这个编号就是常说的存储单元的地址,而读写这个编号的动作就叫做寻 址,通过寻址就能访问到存储区中的任一个能访问的单元,而这个功能是变量或数组等 是不可能代替的。C 语言也因此引入了指针类型的数据类型,专门用来确定其他类型数据的 地址。用一个变量来存放另一个变量的地址,那么用来存放变量地址的变量称为“指针变量”。 如用变量 STRIP 来存放文章开头的 STR 变量的地址 51H,变量 STRIP 就是指针变量。下面 用一个图表来说明变量的指针和指针变量两个不一样的概念。

  C51单片机

  变量的指针就是变量的地址,用取地址运算符‘&’取得赋给指针变量。&STR 就是把 变量 STR 的地址取得。用语句 STRIP = &STR 就能把所取得的 STR 指针存放在 STRIP 指 针变量中。STRIP 的值就变为 51H。可见指针变量的内容是另一个变量的地址,地址所属的 变量称为指针变量所指向的变量。

  要访问变量 STR 除了能用‘STR’这个变量名来访问之外,还能用变量地址来访 问。方法是先用&STR 取变量地址并赋于 STRIP 指针变量,然后就能用*STRIP 来对 STR 进行访问了。‘*’是指针运算符,用它能取得指针变量所指向的地址的值。在上图中指针 变量 STRIP 所指向的地址是 51H,而 51H 中的值是 40H,那么*STRIP 所得的值就是 40H。 使用指针变量之前也和使用其它类型的变量那样要求先定义变量,而且形式也相类似,

  一般的形式如下:

  数据类型 [存储器类型] * 变量名;

  unsigned char xdata *pi //指针会占用二字节,指针自身存放在编译器默认存储区,指

  向 xdata 存储区的 char 类型

  unsigned char xdata * data pi; //除指针自身指定在 data 区,其它同上

  int * pi; //定义为一般指针,指针自身存放在编译器默认存储区,占三个字节 在定义形式中“数据类型”是指所定义的指针变量所指向的变量的类型。“存储器类型”

  是编译器编译时的一种扩展标识,它是可选的。在没有“存储器类型”选项时,则定义为一

  般指针,如有“存储器类型”选项时则定义为基于存储器的指针。限于 51 芯片的寻址范围,

  指针变量最大的值为 0xFFFF,这样就决定了一般指针在内存会占用 3 个字节,第一字节存 放该指针存储器类型编码,后两个则存放该指针的高低位址。而基于存储器的指针因为不用 识别存储器类型所以会占一或二个字节,idata,data,pdata 存储器指针占一个字节,code,xdata 则会占二个字节。由上可知,明确的定义指针,能节省存储器的开销,这在严格要求程序 体积的项目中很有用处。

  指针的使用方法很多,限于篇幅以上只能对它做一些基础的介绍。下面用在讲述常量时 的例程改动一下,用以说明指针的基本使用方法。

  #include 《AT89X51.H》 //预处理文件里面定义了特殊寄存器的名称如 P1 口定义为 P1

  void main(void)

  {

  //定义花样数据,数据存放在片内 CODE 区中

  unsigned char code design[]={0xFF,0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F,

  0x7F,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,0xFE,0xFF,

  0xFF,0xFE,0xFC,0xF8,0xF0,0xE0,0xC0,0x80,0x0,

  0xE7,0xDB,0xBD,0x7E,0xFF};

  unsigned int a; //定义循环用的变量

  unsigned char b;

  unsigned char code * dsi; //定义基于 CODE 区的指针

  do{

  dsi = &design[0]; //取得数组第一个单元的地址

  for (b=0; b《32; b++)

  {

  }

  }while(1);

  }

  for(a=0; a《30000; a++); //延时一段时间

  P1 = *dsi; //从指针指向的地址取数据到 P1 口

  dsi++; //指针加一,

  为了能清楚的了解指针的工作原理,能使用 keil uv2 的软件仿真器查看各变量和存储器的

  值。编译程序并执行,然后打开变量窗口,如图。用单步执行,就能查到到指针的变量。 如图中所示的是程序中循环执行到第二次,这个时候指针 dsi 指向 c:0x0004 这个地址,这个地址 的值是 0xFE。在存储器窗口则能察看各地址单元的值。使用这种方法不但在学习时能 帮助更好的了解语法或程序的工作,而且在实际使用中更能让你更快更准确的编写程序或解 决程序中的问题。

  C51单片机



  第十七课、C51结构、联合和枚举的使用

  前面的文章中介绍了 C 语言的基本数据类型,为了更有效的处理更复杂的数据,C 语 言引入了构造类型的数据类型。构造类型就是将一批各种类型的数据放在一起形成一种特殊 类型的数据。之前讨论过的数组也算是一种构造类型的数据,单片机c语言 中的构造类型还有结构、 枚举和联合。

  结构

  结构是一种数据的集合体,它能按需要将不一样类型的变量组合在一起,整个集合体用 一个结构变量名表示,组成这个集合体的各个变量称为结构成员。理解结构的概念,能用 班级和学生的关系去理解。班级名称就相当于结构变量名,它代表所有同学的集合,而每个 同学就是这个结构中的成员。使用结构变量时,要先定义结构类型。一般定义格式如下:

  struct 结构名 {结构元素表};

  例子:struct FileInfo

  {

  unsigned char FileName[4]; unsigned long Date; unsigned int Size;

  }

  上面的例子中定义了一个简单的文件信息结构类型,它可用于定义用于简单的单片机文 件信息,结构中有三个元素,分别用于操作文件名、日期、大小。因为结构中的每个数据成 员能使用不一样的数据类型,所以要对每个数据成员进行数据类型定义。定义好一个结构类 型后,能按下面的格式进行定义结构变量,要注意的是只有结构变量才能参与程序的执 行,结构类型只是用于说明结构变量是属于那一种结构。

  struct 结构名 结构变量名 1,结构变量名 2……结构变量 N; 例子:struct FileInfo NewFileInfo, OleFileInfo;

  通过上面的定义 NewFileInfo 和 OleFileInfo 都是 FileInfo 结构,都具有一个字符型数组 一个长整型和一个整形数据。定义结构类型只是给出了这个结构的组织形式,它不会占用存 储空间,也就说结构名是不能进行赋值和运算等操作的。结构变量则是结构中的具体成员, 会占用空间,能对每个成员进行操作。

  结构是允许嵌套的,也就是说在定义结构类型时,结构的元素能由另一个结构构成。 如:

  struct clock

  {

  unsigned char sec, min, hour;

  }

  struct date

  {

  unsigned int year;

  unsigned char month, day;

  struct clock Time; //这是结构嵌套

  }

  struct date NowDate; //定义 data 结构变量名为 NowDate

  开始学习的朋友看到这可能会发问:“各个数据元素要如何引用、赋值呢?”使用结构变量 时是通过对它的结构元素的引用来实现的。引用的方法是使用存取结构元素成员运算符“。” 来连接结构名和元素名,格式如下:

  结构变量名。结构元素

  要存取上例结构变量中的月份时,就要写成 NowDate..year。而嵌套的结构,在引用元 素时就要使用多个成员运算符,一级一级连接到最低级的结构元素。要注意的是在 单片机c语言 中 只能对最低级的结构元素进行访问,而不可能对整个结构进行操作。操作例子:

  NowDate.year = 2005;

  NowDate.month = OleMonth+ 2; //月份数据在旧的基础上加 2

  NowDate.Time.min++; //分针加 1,嵌套时只能引用最低一级元素 一个结构变量中元素的名字能和程序中其他地方使用的变量同名,因为元素是属于它所在 的结构中,使用时要用成员运算符指定。

  结构类型的定义还能有如下的两种格式。

  struct

  {

  结构元素表

  } 结构变量名 1,结构变量名 2……结构变量名 N;

  例:struct

  {

  unsigned char FileName[4]; unsigned long Date; unsigned int Size;

  } NewFileInfo, OleFileInfo;

  这一种定义方式定义没有使用结构名,称为无名结构。通常会用于程序中只有几个确定 的结构变量的场合,不能在其它结构中嵌套。

  另一种定义方式如下:

  struct 结构名

  {

  结构元素表

  } 结构变量名 1,结构变量名 2……结构变量名 N;

  例:struct FileInfo

  {

  unsigned char FileName[4]; unsigned long Date; unsigned int Size;

  } NewFileInfo, OleFileInfo;

  使用结构名能便于阅读程序和便于以后要在定义其它结构中使用。 枚举

  在程序中经常要用到一些变量去做程序中的判断标志。如经常要用一个字符或整型变量

  去储存 1 和 0 做判断条件真假的标志,但我们也许会疏忽这个变量只有当等于 0 或 1 才是有

  效的,而将它赋上别的值,而使程序出错或变的混乱。这个时候能使用枚举数据类型去定义变 量,限制错误赋值。枚举数据类型就是把某些整型常量的集合用一个名字表示,其中的整型 常量就是这种枚举类型变量的可取的合法值。枚举类型的二种定义格式如下:

  enum 枚举名 {枚举值列表} 变量列表;

  例 enum TFFlag {False, True} TFF;

  enum 枚举名 {枚举值列表};

  emum 枚举名 变量列表;

  例 enum Week {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

  enum Week OldWeek, NewWeek;

  看了上面的例子,你也许有一个地方想不通,那就是为什么枚举值不用贬值就能使 用?那是因为在枚举列表中,每一项名称代表一个整数值,在默认的情况下,编译器会自动 为每一项赋值,第一项赋值为 0,第二项为 1…。..如 Week 中的 Sun 为 0,Fri 为 5。C 语言也 允许对各项值做初始化赋值,要注意的是在对某项值初始化后,它的后续的各项值也随之递 增。如:

  enum Week {Mon=1, Tue, Wed, Thu, Fri, Sat, Sun};

  上例的枚举就使 Week 值从 1 到 7,这样会更符合我们的习惯。使用枚举就如变量一样, 但在程序中不能为其赋值。

  联合

  联合同样是 C 语言中的构造类型的数据结构。它和结构类型一样能包含不一样类型的 数据元素,所不一样的是联合的数据元素都是从同一个数据地址开始存放。结构变量占用的内 存大小是该结构中数据元素所占内存数的总和,而联合变量所占用内存大小只是该联合中最 长的元素所占用的内存大小。如在结构中定义了一个 int 和一个 char,那么结构变量就会占

  用 3 个字节的内存,而在联合中同样定义一个 int 和一个 char,联合变量只会占用 2 个字节。 这种能充分利用内存空间的技术叫‘内存覆盖技术’,它能使不一样的变量分时的使用同一 个内存空间。使用联合变量时要注意它的数据元素只能是分时使用,而不能同时使用。举个 简单的例子,程序先为联合中的 int 赋值 1000,后来又为 char 赋值 10,那么这个时候就不能引用

  int 了,不然程序会出错,起作用的是最后一次赋值的元素,而上一次赋值的元素就失效了。 使用中还要注意定义联合变量时不能对它的值初始化、能使用指向联合变量的指针对其操 作、联合变量不能作为函数的参数进行传递,数组和结构能出现在联合中。

  联合类型变量的定义方法和结构的定义方法差不多,只要把关键字 struct 换用 union 就 能了。联合变量的引用方法除也是使用‘。’成员运算符。

  下面就用一个综合的例子说明三种类型的简单使用。

  #include 《AT89X51.H》

  #include 《stdio.h》

  void main(void)

  {

  enum TF {

  False, True} State; //定义一个枚举,使程序更易读

  union File { //联合中包含一数组和结构,

  unsigned char Str[11]; //整个联合共用 11 个字节内存

  struct FN {

  unsigned char Name[6],EName[5];} FileName;

  } MyFile;

  unsigned char Temp;

  SCON = 0x50; //串行口方式 1,允许接收

  TMOD = 0x20; //定时器 1 定时方式 2

  TCON = 0x40; //设定时器 1 开始计数

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

  TL1 = 0xE8; TI = 1;

  TR1 = 1; //启动定时器

  State = True; //这里演示 State 只能赋为 False,True 两个值,其它无效

  //State = 3;这样是错误的

  printf (“Input File Name 5Byte: ”);

  scanf(“%s”, MyFile.FileName.Name); //保存 5 字节字符串要 6 个字节

  printf (“Input File ExtendName 4Byte: ”);

  scanf(“%s”, MyFile.FileName.EName);

  if (State == True)

  {

  printf (“File Name : ”);

  for (Temp=0; Temp《12; Temp++)

  printf (“%c”, MyFile.Str[Temp]); //这里列出所有的字节

  printf (“ Name :”);

  printf (“%s”, MyFile.FileName.Name);

  printf (“ ExtendName :”);

  printf (“%s”, MyFile.FileName.EName);

  }

  while(1);

  }

  图 17-1 所示是运行的结果,A 中所示是说明例程中联合中的数组和结构占用的是同一段地址的内存空间,而结构中的两数组是各占两段不一样内存空间。

  C51单片机

 

  图 17-1

 

  在此简单的单片机C语言教程就结束了,请关注电子发烧友网后续技术报道。



第十八课、附录(运算符优先级和结合性等

附表1-2 C51编译器的扩展关键字

 

关键字 用 途 说 明
auto
存储种类说明
用以说明局部变量,缺省值为此
break
程序语句
退出最内层循环
case
程序语句
Switch语句中的选择项
char
数据类型说明
单字节整型数或字符型数据
const
存储类型说明
在程序执行过程中不可更改的常量值
continue
程序语句
转向下一次循环
default
程序语句
Switch语句中的失败选择项
do
程序语句
构成do..while循环结构
double
数据类型说明
双精度浮点数
else
程序语句
构成if..else选择结构
enum
数据类型说明
枚举
extern
存储种类说明
在其他程序模块中说明了的全局变量
flost
数据类型说明
单精度浮点数
for
程序语句
构成for循环结构
goto
程序语句
构成goto转移结构
if
程序语句
构成if..else选择结构
int
数据类型说明
基本整型数
long
数据类型说明
长整型数
register
存储种类说明
使用CPU内部寄存的变量
return
程序语句
函数返回
short
数据类型说明
短整型数
signed
数据类型说明
有符号数,二进制数据的最高位为符号位
sizeof
运算符
计算表达式或数据类型的字节数
static
存储种类说明
静态变量
struct
数据类型说明
结构类型数据
swicth
程序语句
构成switch选择结构
typedef
数据类型说明
重新进行数据类型定义
union
数据类型说明
联合类型数据
unsigned
数据类型说明
无符号数数据
void
数据类型说明
无类型数据
volatile
数据类型说明
该变量在程序执行中可被隐含地改变
while
程序语句
构成while和do..while循环结构

 

附表1-1 ANSIC标准关键字

 

 

关键字
用 途
说 明
bit
位标量声明
声明一个位标量或位类型的函数
sbit
位标量声明
声明一个可位寻址变量
Sfr
特殊功能寄存器声明
声明一个特殊功能寄存器
Sfr16
特殊功能寄存器声明
声明一个16位的特殊功能寄存器
data
存储器类型说明
直接寻址的内部数据存储器
bdata
存储器类型说明
可位寻址的内部数据存储器
idata
存储器类型说明
间接寻址的内部数据存储器
pdata
存储器类型说明
分页寻址的外部数据存储器
xdata
存储器类型说明
外部数据存储器
code
存储器类型说明
程序存储器
interrupt
中断函数说明
定义一个中断函数
reentrant
再入函数说明
定义一个再入函数
using
寄存器组定义
定义芯片的工作寄存器

 


 
附录二 AT89C51特殊功能寄存器列表(适用于同一架构的芯片)
带*号的特殊功能寄存器都是可以位寻址的寄存器

 

符 号
地 址
注 释
*ACC
E0H
累加器
*B
F0H
乘法寄存器
*PSW
D0H
程序状态字
SP
81H
堆栈指针
DPL
82H
数据存储器指针低8位
DPH
83H
数据存储器指针高8位
*IE
A8H
中断允许控制器
*IP
D8H
中断优先控制器
*P0
80H
端口0
*P1
90H
端口1
*P2
A0H
端口2
*P3
B0H
端口3
PCON
87H
电源控制及波特率选择
*SCON
98H
串行口控制器
SBUF
99H
串行数据缓冲器
*TCON
88H
定时器控制
TMOD
89H
定时器方式选择
TL0
8AH
定时器0低8位
TL1
8BH
定时器1低8位
TH0
8CH
定时器0低8位
TH1
8DH
定时器1高8位
 
附录三 运算符优先级和结合性
 
级 别
类 别
名 称
运算符
结合性
1
强制转换、数组、
结构、联合
强制类型转换
( )
右结合
下标
[ ]
存取结构或联合成员
->或.
2
 辑
逻辑非
!
左结合
字 位
按位取反
~
增 量
加一
++
减 量
--
指 针
取地址
&
取内容
*
算 术
单目减
-
长度计算
长度计算
sizeof
3
算 术
*
右结合
/
取模
%
4
算术和指针运算
+
-
5
字 位
左移
<<
右移
>>
6
关系
大于等于
>=
大于
>
小于等于
<=
小于
<
7
恒等于
==
不等于
!=
8
字 位
按位与
&
9
按位异或
^
10
按位或
|
11
 辑
逻辑与
&&
左结合
12
逻辑或
||
13
条 件
条件运算
?:
14
赋 值
赋值
=
复合赋值
Op=
15
逗 号
逗号运算
,
右结合
 















 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
评论(0)
发评论
gnzt 2016-03-09
0 回复 举报
好文章,谢谢 收起回复
hou_flyaway 2013-01-08
0 回复 举报
本科的时候学过,好久不用就快忘了,, 收起回复
全部评论

全部0条评论

快来发表一下你的评论吧 !

登录/注册
×
20
完善资料,
赚取积分